﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.ServiceModel;
using LiSA_Server.Base.AbstractActioners;
using LiSA_Server.Base.NHibernate;
using LiSA_Service.Faults;
using LiSA_Service.Users.Dto;
using LiSA_Service.Users.Dto.Commands;
using LiSA_Service.Users.Dto.Queries;
using LiSA_Service.Users.Exceptions;
using LiSA_Service.Users.Models;
using NHibernate.Criterion;

namespace LiSA_Service.Users.Actioners
{
    public class UserActioner : BaseActioner
    {
        /// <summary>
        /// Checks to see if a user with given parameters is in the system
        /// </summary>
        /// <param name="userName">The Users full Username</param>
        /// <param name="UserID">The Users Guid ID</param>
        /// <returns>False if there is no User with either of the aprameters, True otherwise</returns>
        public bool CheckUser(string userName, Guid UserID)
        {
            var result = false;

            NHibernateProvider.Instance.WithSession(
                hsession =>
                {
                    var theUserByID = hsession.Get<Models.User>(UserID);
                    if (theUserByID != null)
                    {
                        result = true;
                    }
                    var users = hsession.QueryOver<Models.User>().List().ToList();
                    if (users.Any(x=>x.UserName == userName))
                    {
                        result = true;
                    }

                });
            return result;
        }


        /// <summary>
        /// Creates a new User and the attached Person and Organization, if necessary
        /// </summary>
        /// <param name="createUserCommand"></param>
        public void CreateUser(Dto.Commands.CreateUserCommand createUserCommand)
        {
            var potentialNewOrganization = createUserCommand.User.Organization;
            potentialNewOrganization.ID = Guid.NewGuid();
            potentialNewOrganization.CreatedDateTime = DateTime.Now;

            var personToSave = createUserCommand.User.Person;
            personToSave.ID = Guid.NewGuid();
            personToSave.Organization = potentialNewOrganization.ID;
            personToSave.CreatedDateTime = DateTime.Now;

            var theUserToSave = new LiSA_Service.Users.Models.User
            {
                UserName = createUserCommand.User.UserName,
                CreatedDateTime = DateTime.Now,
                Email = createUserCommand.User.Email,
                EncryptedPassword = createUserCommand.User.NewEncryptedPassword,
                ID = Guid.NewGuid(),
                IsActive = true,
                PersonID = personToSave.ID
            };

            // Dive into the database
            NHibernateProvider.Instance.WithSessionAndTransaction(
               hsession =>
               {
                   var allUsers = hsession.QueryOver<Models.User>().Where(x => x.UserName == theUserToSave.UserName);
                   if (allUsers.List().ToList().Any())
                   {
                       throw new UserNameAlreadyExistsException(string.Format("The username {0} is already taken.", theUserToSave.UserName));
                   }
                   var name = potentialNewOrganization.Name;

                   // Save Organization if one wityh the same name does not exist. Else use old.
                   var orgs = hsession.QueryOver<Organization>().Where(x => x.Name.IsInsensitiveLike(name.Trim(), MatchMode.Exact)).Take(1).SingleOrDefault();
                   if (orgs == null)
                   {
                       hsession.Save(potentialNewOrganization);
                   }
                   else
                   {
                       personToSave.Organization = orgs.ID;
                   }

                   // Save the Person
                   hsession.Save(personToSave);

                   // Save the User
                   hsession.Save(theUserToSave);
               });
        }

        /// <summary>
        /// Gets a user DTO by ID of the Models.User
        /// </summary>
        /// <param name="userID">ID of the User</param>
        /// <returns>A Dto.User</returns>
        public Dto.User GetUserByID(Guid userID)
        {
            var result = new Dto.User();
            if (userID == Guid.Empty)
            {
                return result;
            }
            NHibernateProvider.Instance.WithSession(
                hsession =>
                {
                    var theUser = hsession.Get<Models.User>(userID);
                    var theUserPerson = new Person();
                    var permissions = hsession.QueryOver<Models.Permission>().Where(x => x.UserID == theUser.ID);
                    try
                    {
                        theUserPerson = hsession.Get<Models.Person>(theUser.PersonID);
                    }
                    catch (Exception)
                    {
                    }

                    // Create Dto
                    result.ID = userID;
                    result.UserName = theUser.UserName;
                    result.IsActive = theUser.IsActive;
                    result.Email = theUser.Email;
                    result.Person = theUserPerson;
                    result.FullName = theUserPerson.FirstName + " " + theUserPerson.MiddleName + " " +
                                      theUserPerson.LastName;
                    result.Organization = new Organization { Name = "No affiliation", Country = "N/A", ID = Guid.Empty };
                    result.Permissions = permissions.List().ToArray();
                    try
                    {
                        result.Organization = hsession.Get<Organization>(theUserPerson.Organization);
                    }
                    catch (Exception)
                    {
                    }
                });

            return result;
        }

        /// <summary>
        /// Edits a user
        /// </summary>
        /// <param name="dtoUser">The Dto.User object containing values to be edited</param>
        /// <param name="encryptedPassword">User being edited's current encrypted password</param>
        /// <returns>Success or not</returns>
        public bool EditUser(Dto.User dtoUser, string encryptedPassword)
        {
            var success = false;

            NHibernateProvider.Instance.WithSessionAndTransaction(
                hsession =>
                {
                    var theUser = hsession.Get<Models.User>(dtoUser.ID);
                    var theUserPerson = hsession.Get<Models.Person>(theUser.PersonID);

                    // Ensure you're allowed to edit the user. If not, abort!
                    if (theUser.EncryptedPassword != encryptedPassword)
                    {
                        throw new FaultException<LoginFault>(new LoginFault("Incorrect Password"));
                    }

                    // Ensure each edited value is not empty.
                    if (string.IsNullOrEmpty(dtoUser.Email))
                    {
                        throw new FaultException<InvalidParametersFault>(new InvalidParametersFault("No E-mail"));
                    }
                    if (string.IsNullOrEmpty(dtoUser.UserName))
                    {
                        throw new FaultException<InvalidParametersFault>(new InvalidParametersFault("No username"));
                    } 
                    if (dtoUser.UserName != theUser.UserName)
                    {
                        throw new FaultException<InvalidParametersFault>(new InvalidParametersFault("Usernames do not match"));
                    } 
                    if (string.IsNullOrEmpty(dtoUser.Person.FirstName))
                    {
                        throw new FaultException<InvalidParametersFault>(new InvalidParametersFault("No first name"));
                    } 
                    if (string.IsNullOrEmpty(dtoUser.Person.LastName))
                    {
                        throw new FaultException<InvalidParametersFault>(new InvalidParametersFault("No last name"));
                    }
                    if (dtoUser.Person.ID != theUserPerson.ID)
                    {
                        throw new FaultException<InvalidParametersFault>(new InvalidParametersFault("Person ID's do not match"));
                    }

                    // Everything looks acceptable. Update DB objects
                    theUser.IsActive = dtoUser.IsActive;
                    theUser.Email = dtoUser.Email;
                    theUser.EncryptedPassword = dtoUser.NewEncryptedPassword;
                    hsession.Update(theUser);
                    theUserPerson = dtoUser.Person;
                    hsession.Update(theUserPerson);
                    success = true;
                });


            return success;
        }

        /// <summary>
        /// Changes a users active-status
        /// </summary>
        /// <param name="IsActive">New status</param>
        /// <param name="userID">ID of the suer whos status is to be changed</param>
        /// <returns>Success or not</returns>
        public bool ChangeUserStatus(bool IsActive, Guid userID)
        {
            var success = false;

            NHibernateProvider.Instance.WithSessionAndTransaction(
                hsession =>
                {
                    var theUser = hsession.Get<Models.User>(userID);
                    try
                    {
                        theUser.IsActive = IsActive;
                        hsession.Update(theUser);
                        success = true;
                    }
                    catch (Exception)
                    {
                        throw new FaultException<InvalidParametersFault>(new InvalidParametersFault("No user with that ID"));
                    }
                });

            return success;
        }

        /// <summary>
        /// Ensures the database cotnains the Default User needed to create an account
        /// </summary>
        public void EnsureDefaultUser()
        {
            NHibernateProvider.Instance.WithSessionAndTransaction(
                hsession =>
                {
                    var users = hsession.QueryOver<Models.User>().List().ToList();

                    //if none found, make him
                    if (users.Any(x => x.UserName == "UserCreator"))
                    {
                        return;
                    }

                    {
                        var createrPerson = new Person
                        {
                            ID = Guid.NewGuid(),
                            FirstName = "Benden",
                            LastName = "Weyr",
                            CreatedDateTime = DateTime.Now,
                            Organization = Guid.Empty,
                            Birthday = DateTime.Now
                        };
                        var newUser = new Models.User
                        {
                            ID = Guid.NewGuid(),
                            CreatedDateTime = DateTime.Now,
                            Email = "N/A",
                            IsActive = true,
                            EncryptedPassword = "1234",
                            PersonID = createrPerson.ID,
                            UserName = "UserCreator",
                        };
                        hsession.Save(createrPerson);
                        hsession.Save(newUser);
                    }
                });
        }

        /// <summary>
        /// Gets all suers in the system
        /// </summary>
        /// <returns>A lsit of the users</returns>
        public List<Dto.User> GetAllUsers()
        {
            var rawUserID = new List<Guid>();
            NHibernateProvider.Instance.WithSession(
                hsession =>
                {
                    rawUserID = hsession.QueryOver<Models.User>().List().Select(x => x.ID).ToList();
                });
            var returnUsers = rawUserID.Select(GetUserByID).ToList();

            //Remove the default user
            if (returnUsers.Any(x => x.UserName == "UserCreator"))
                returnUsers.Remove(returnUsers.SingleOrDefault(x => x.UserName == "UserCreator"));
            return returnUsers;
        }

        /// <summary>
        /// Gets a User by the UserName
        /// </summary>
        /// <param name="userName">UserName of the User</param>
        /// <returns>A User DTO</returns>
        public Dto.User GetUserByUserName(string userName)
        {
            var result = new Dto.User();
            if (userName == string.Empty)
            {
                return result;
            }

            NHibernateProvider.Instance.WithSession(
                hsession =>
                {
                    var allUsers = hsession.QueryOver<Models.User>();
                    var theUser = allUsers.Where(x => x.UserName== userName).SingleOrDefault();
                    if (theUser == null)
                    {
                        result = null;
                        return;
                    }

                    var theUserPerson = new Person();
                    var permissions = hsession.QueryOver<Models.Permission>().Where(x => x.UserID == theUser.ID);
                    try
                    {
                        theUserPerson = hsession.Get<Models.Person>(theUser.PersonID);
                    }
                    catch (Exception)
                    {
                    }
                    if (theUserPerson.ID == Guid.Empty)
                    {
                        throw new FaultException<LoginFault>(new LoginFault("Invalid minimum date."));
                    }
                    // Create Dto
                    result.ID = theUser.ID;
                    result.UserName = theUser.UserName;
                    result.IsActive = theUser.IsActive;
                    result.Email = theUser.Email;
                    result.Person = theUserPerson;
                    result.FullName = theUserPerson.FirstName + " " + theUserPerson.MiddleName + " " +
                                      theUserPerson.LastName;
                    result.Organization = new Organization { Name = "No affiliation", Country = "N/A", ID = Guid.Empty };
                    result.Permissions = permissions.List().ToArray();
                    try
                    {
                        result.Organization = hsession.Get<Organization>(theUserPerson.Organization);
                    }
                    catch (Exception)
                    {
                    }
                });

            return result;
        }

        #region helper methods
        /// <summary>
        /// Builds a Dto.User if we already have the Models.User
        /// </summary>
        /// <param name="theUser">The Models.User object</param>
        /// <returns>A corresponding Dto.User</returns>
        private Dto.User BuildUser(Models.User theUser)
        {

            var result = new Dto.User();
            NHibernateProvider.Instance.WithSession(
                hsession =>
                {
                    var theUserPerson = hsession.Get<Models.Person>(theUser.PersonID);
                    var permissions = hsession.QueryOver<Models.Permission>().Where(x => x.UserID == theUser.ID);

                    // Create Dto result.ID = userID;
                    result.UserName = theUser.UserName;
                    result.IsActive = theUser.IsActive;
                    result.Email = theUser.Email;
                    result.Person = theUserPerson;
                    result.FullName = theUserPerson.FirstName + " " + theUserPerson.MiddleName + " " +
                                      theUserPerson.LastName;
                    result.Organization = hsession.Get<Organization>(theUserPerson.Organization) ?? new Organization { Name = "No affiliation", Country = "N/A", ID = Guid.Empty };
                    result.Permissions = permissions.List().ToArray();
                });

            return result;
        }
        #endregion

    }
}